import { RenderData } from "./classes/RadarRenderData";
import { parseColorFlag, parseNum2HexColor } from "./fileLoader/loadSctFile";
import { Coordinate } from "./coordParser/Coordinate";
import { PluginData } from "./classes/ExternalPluginData";
import { ISctGeo } from "./interfaces/ISctGeo";
import { ISctRegion } from "./interfaces/ISctRegion";
import { ISctVorNdb } from "./interfaces/ISctVorNdb";
import { IAsrDrawItem } from "./interfaces/IAsrDrawItem";
import { Line } from "./interfaces/Line";
import { ISctRunway } from "./interfaces/ISctRunway";

class RadarCanvasDrawer {
    private ctx: CanvasRenderingContext2D | null = null;
    private canvas: HTMLCanvasElement | null = null;
    private drawDebounceTimer: number = 0;
    private scaleIndex = 1;
    private offsetX = 0;
    private offsetY = 0;
    private rotation = 0;
    private initDraw: boolean = true;

    constructor() {
        window.addEventListener('resize', () => this.draw());
    }

    setRotation(angle: number): void {
        this.rotation = angle;
    }

    resetInitDraw(): void {
        this.initDraw = true;
    }

    updateScaleIndex(scaleIndex: number) {
        this.scaleIndex = scaleIndex;
        this.draw();
    }

    getCloserToMouse(e: WheelEvent) {
        if (this.canvas == null) return;
        const mouseX = e.clientX - this.canvas.getBoundingClientRect().left;
        const mouseY = e.clientY - this.canvas.getBoundingClientRect().top;

        const newScaleIndex = e.deltaY < 0 ? 1.2 * this.scaleIndex : this.scaleIndex / 1.2;
        const ratio = 1 - newScaleIndex / this.scaleIndex;
        this.offsetX += (mouseX - this.offsetX) * ratio;
        this.offsetY += (mouseY - this.offsetY) * ratio;
    }

    updateScaleIndexByWheelEvent(e: WheelEvent, mouseCenter: boolean = true) {
        if (e.deltaY < 0) {
            const r = this.scaleIndex * 1.2;
            if (r < 250000) {
                mouseCenter && this.getCloserToMouse(e);
                this.updateScaleIndex(r);
            }
        } else {
            const r = this.scaleIndex / 1.2;
            if (r > 1) {
                mouseCenter && this.getCloserToMouse(e);
                this.updateScaleIndex(r);
            }
        }
    }

    bindCanvas(canvas: HTMLCanvasElement) {
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");
        if (this.ctx != null) {
            this.ctx.imageSmoothingEnabled = false;
        }
    }

    draw() {
        clearTimeout(this.drawDebounceTimer);
        this.drawDebounceTimer = setTimeout(() => this._draw());
    }

    setCamera(x: number, y: number) {
        this.offsetX = x;
        this.offsetY = y;
    }

    getMousePosition(e: MouseEvent): Coordinate {
        const result = new Coordinate();
        const clientX = e.clientX;
        const clientY = e.clientY;
        // 经度
        result.longitude = -this.offsetX + clientX / this.scaleIndex;
        result.latitude = -(this.offsetY - clientY / this.scaleIndex);
        return result;
    }

    setCameraByMouseEvent(e: MouseEvent) {
        const x = this.offsetX + e.movementX;
        const y = this.offsetY + e.movementY;
        this.setCamera(x, y);
    }

    private _draw() {
        console.time('radar draw')
        if (this.ctx == null || this.canvas == null) {
            console.error('ctx or canvas not found');
            return;
        }
        if (this.initDraw) {
            const coord1LngArray: number[] = [];
            const coord2LatArray: number[] = [];
            const coord1LatArray: number[] = [];
            const coord2LngArray: number[] = [];
            for (const asr of RenderData.asr.values()) {
                coord1LngArray.push(asr.windowArea.coord1.longitude);
                coord1LatArray.push(asr.windowArea.coord1.latitude);
                coord2LatArray.push(asr.windowArea.coord2.latitude);
                coord2LngArray.push(asr.windowArea.coord2.longitude);
            }
            this.offsetX = Math.min(...coord1LngArray);
            this.offsetY = Math.max(...coord2LatArray);
            this.scaleIndex = (Math.max(...coord2LngArray) - Math.max(...coord1LngArray)) * (this.canvas?.width ?? 1);
            this.initDraw = false;
        }
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
        this.ctx.save();
        // this.ctx.rotate(this.rotation * Math.PI / 180);
        // this.ctx.translate(window.innerWidth / 2, window.innerHeight / 2);
        this.ctx.translate(this.offsetX, this.offsetY);
        // this.ctx.translate(this.offsetX * (this.scaleIndex - 1), this.offsetY * (this.scaleIndex - 1));
        this.ctx.strokeStyle = '#FFFFFF';
        this.ctx.fillStyle = '#FFFFFF';
        this.ctx.lineWidth = 0.5;
        this.ctx.font = '10px "Monaco"';
        for (const pilot of RenderData.vatsimData.pilots) {
            RenderData.groundAircraftSymbolDrawer.draw(this.ctx, Coordinate.fromNumber(-pilot.latitude, pilot.longitude), this.scaleIndex, undefined, pilot.heading);
        }
        if (RenderData.asr.size > 0) {
            for (const asr of RenderData.asr.values()) {
                for (const item of asr.items) {
                    if (!item.draw) continue;
                    const symbol = RenderData.symbols.get(`${item.type}-${item.drawItem}`);
                    if (symbol !== undefined) {
                        this.ctx.strokeStyle = parseNum2HexColor(symbol.color);
                        this.ctx.fillStyle = parseNum2HexColor(symbol.color);
                    }
                    if (item.type == 'Fixes') {
                        const pos = RenderData.sct.fix.get(item.name);
                        if (pos != undefined && symbol != undefined) {
                            if (item.drawItem == 'symbol') {
                                RenderData.fixSymbolDrawer.draw(this.ctx, pos.coord, this.scaleIndex, symbol, this.rotation);
                            }
                            this.ctx.fillText(pos.name, pos.coord.longitude * this.scaleIndex, pos.coord.latitude * this.scaleIndex);
                        }
                    } else if (item.type == 'High airways') {
                        const aw = RenderData.sct.hiAirway.get(item.name);
                        if (aw != undefined) {
                            for (const line of aw.lines) {
                                const l = new Path2D;
                                l.moveTo(line[0].longitude * this.scaleIndex, line[0].latitude * this.scaleIndex);
                                l.lineTo(line[1].longitude * this.scaleIndex, line[1].latitude * this.scaleIndex);
                                this.ctx.stroke(l);
                            }
                        }
                    } else if (item.type === 'Geo') {
                        const geo = RenderData.sct.geo.get(item.name);
                        if (geo != undefined) {
                            this.geoDrawer(geo, this.ctx);
                        }
                    } else if (item.type === 'Regions') {
                        const region = RenderData.sct.region.get(item.name);
                        if (region != undefined) {
                            this.regionDrawer(region, this.ctx);
                        }
                    } else if (item.type === 'VORs') {
                        this.drawVorFromAsr(item, this.ctx);
                    } else if (item.type === 'Free Text') {
                        this.drawFreeTextFromAsr(item, this.ctx);
                    } else if (item.type === 'Sids') {
                        this.drawSidFromAsr(item, this.ctx);
                    } else if (item.type === 'Stars') {
                        this.drawStarFromAsr(item, this.ctx);
                    } else if (item.type === 'Runways') {
                        this.drawRunwayFromAsr(item, this.ctx);
                    } else if (item.type === 'NDBs') {
                        this.drawNdbFromAsr(item, this.ctx);
                    }
                }
            }
        } else {
            for (const region of RenderData.sct.region.values()) {
                this.regionDrawer(region, this.ctx);
            }
            for (const geo of RenderData.sct.geo.values()) {
                this.geoDrawer(geo, this.ctx);
            }
            for (const fix of RenderData.sct.fix.values()) {
                const symbol = RenderData.symbols.get(`Fixes-symbol`);
                if (symbol == undefined) continue;
                RenderData.fixSymbolDrawer.draw(this.ctx, fix.coord, this.scaleIndex, symbol);
                this.ctx.fillText(fix.name, fix.coord.longitude * this.scaleIndex + 4, fix.coord.latitude * this.scaleIndex);
            }
            for (const vor of RenderData.sct.vor.values()) {
                const symbol = RenderData.symbols.get(`VORs-symbol`);
                RenderData.vorSymbolDrawer.draw(this.ctx, vor.coord, this.scaleIndex, symbol);
                this.ctx.fillText(vor.name, vor.coord.longitude * this.scaleIndex, vor.coord.latitude * this.scaleIndex);
            }
            for (const ndb of RenderData.sct.ndb.values()) {
                const symbol = RenderData.symbols.get(`NDBs-symbol`);
                RenderData.ndbSymbolDrawer.draw(this.ctx, ndb.coord, this.scaleIndex, symbol);
                this.ctx.fillText(ndb.name, ndb.coord.longitude * this.scaleIndex, ndb.coord.latitude * this.scaleIndex);
            }
        }

        this.ctx.fillStyle = 'yellow';
        for (const ft of RenderData.ese.freetext) {
            this.ctx.fillText(ft.text, ft.coord.longitude * this.scaleIndex, ft.coord.latitude * this.scaleIndex);
        }
        for (const plugin of PluginData.plugins) {
            plugin.onRadarFrame(this.ctx, this.scaleIndex);
        }
        this.ctx.restore();
        console.timeEnd('radar draw')
    }

    private drawRunwayFromAsr(item: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const pkey1Builder = (rw: ISctRunway) => `${rw.airportCode} ${rw.airportName} ${rw.endPointA}-${rw.endPointB}`;
        const pkey2Builder = (rw: ISctRunway) => `${rw.airportCode} ${rw.airportName} ${rw.endPointB}-${rw.endPointA}`;
        const rw = RenderData.sct.runway.find(rw => item.name === pkey1Builder(rw) || item.name === pkey2Builder(rw));
        if (rw === undefined) return;
        const p = new Path2D;
        p.moveTo(rw.coordA.longitude * this.scaleIndex, rw.coordA.latitude * this.scaleIndex);
        p.lineTo(rw.coordB.longitude * this.scaleIndex, rw.coordB.latitude * this.scaleIndex);
        context.stroke(p);
        context.fillText(rw.endPointA, rw.coordA.longitude * this.scaleIndex, rw.coordA.latitude * this.scaleIndex);
        context.fillText(rw.endPointB, rw.coordB.longitude * this.scaleIndex, rw.coordB.latitude * this.scaleIndex);
    }

    private drawFreeTextFromAsr(freeText: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const ft = RenderData.ese.freetext.find(ft => ft.text === freeText.name);
        if (ft === undefined) return;
        context.save();
        context.rotate(0);
        context.fillText(freeText.name, ft.coord.longitude * this.scaleIndex, ft.coord.latitude * this.scaleIndex);
        context.restore();
    }

    private geoDrawer(geo: ISctGeo, context: CanvasRenderingContext2D): void {
        for (let i = 0; i < geo.items.length; i++) {
            const edge = geo.items[i];
            context.strokeStyle = parseColorFlag(edge.flag);
            const l = new Path2D;
            const x1 = edge.line[0].longitude * this.scaleIndex;
            const y1 = edge.line[0].latitude * this.scaleIndex;
            l.moveTo(x1, y1);
            const x2 = edge.line[1].longitude * this.scaleIndex;
            const y2 = edge.line[1].latitude * this.scaleIndex;
            l.lineTo(x2, y2);
            geo.items[i].path = l;
            context.stroke(l);
        }
    }

    private drawLines(lines: Line[], context: CanvasRenderingContext2D): void {
        const p = new Path2D;
        for (const line of lines) {
            p.moveTo(line[0].longitude * this.scaleIndex, line[0].latitude * this.scaleIndex);
            p.lineTo(line[1].longitude * this.scaleIndex, line[1].latitude * this.scaleIndex);
        }
        context.stroke(p);
    }

    private drawSidFromAsr(item: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const k = RenderData.sct.sid.get(item.name);
        if (k === undefined) return;
        this.drawLines(k.lines, context);
    }
    private drawStarFromAsr(item: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const k = RenderData.sct.star.get(item.name);
        if (k === undefined) return;
        this.drawLines(k.lines, context);
    }
    private regionDrawer(region: ISctRegion, context: CanvasRenderingContext2D): void {
        for (let i = 0; i < region.items.length; i++) {
            const block = region.items[i];
            context.fillStyle = parseColorFlag(block.flag);
            const path = new Path2D;
            path.moveTo(block.coords[0].longitude * this.scaleIndex, block.coords[0].latitude * this.scaleIndex);
            for (let i = 1; i < block.coords.length; i++) {
                const point = block.coords[i];
                path.lineTo(point.longitude * this.scaleIndex, point.latitude * this.scaleIndex);
            }
            path.closePath();
            region.items[i].path = path;
            context.fill(path);
        }
    }

    private vorDrawer(vor: ISctVorNdb, context: CanvasRenderingContext2D, drawItem: string): void {
        const symbol = RenderData.symbols.get(`VORs-symbol`);
        if (drawItem === 'symbol') {
            RenderData.vorSymbolDrawer.draw(context, vor.coord, this.scaleIndex, symbol);
        } else if (drawItem === 'name') {
            context.fillText(vor.name, vor.coord.longitude * this.scaleIndex, vor.coord.latitude * this.scaleIndex);
        }
    }

    private drawVorFromAsr(item: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const vor = RenderData.sct.vor.get(item.name);
        if (vor == undefined) return;
        this.vorDrawer(vor, context, item.drawItem);
    }
    private drawNdbFromAsr(item: IAsrDrawItem, context: CanvasRenderingContext2D): void {
        const vor = RenderData.sct.ndb.get(item.name);
        if (vor == undefined) return;
        this.vorDrawer(vor, context, item.drawItem);
    }
}

export const RadarScreen = new RadarCanvasDrawer();